<?php
/**
 * Created by PhpStorm.
 * User: kezihang
 * Date: 2020/9/3
 * Time: 10:33
 */

class LockCache extends RedisCache{


    protected $lockId; //记录加锁客户端ID

    protected $lockKey = 'app_ad_data_lock_key_20220518';
    protected $lockTimeout = 10;//间隔多少秒尝试下一把锁
    protected $lockRunTimeout = 180;//脚本运行保护时间
    protected $lockExpire = 600;//锁缓存时间,$lockExpire > $lockRunTimeout > $lockTimeout
    //锁列表,保持有值
    protected $lockList=array(
        'app_ad_data_lock_user',
    );

    /****
     * @param string $key 锁标识
     * @param int $expire  锁过期时间
     * @param int $retry   尝试获取锁次数
     * @param int $sleep   多久尝试一次 (微秒)
     */
    public function lock($key="btgo_ticket", $expire=5,$retry=5,$sleep=200000 ){
        $res=false;
        while ($retry -- > 0){
            //生成唯一锁ID，解锁需持有此ID
            $value =  $this->generateUniqueLockId();
            $res=$this->redis->set($key,$value,array('nx','ex'=>$expire));
            if ($res){
                $this->lockId=$value;
                break;
            }
            usleep($sleep);
        }
        return $res;
    }

    /**
     * 释放锁
     * @param String $key 锁标识
     * @return Boolean
     */
    public function unlock($key, $continued = false)
    {
        if ($continued === true) {
            if (!empty($this->lockId)) {
                global $redisConfig;
                $cache = new Redis();
                $redisConfig = $redisConfig['default'];
                $cache->connect($redisConfig['server'], $redisConfig['port']);
                $value = $cache->get($key);
                if ($value == $this->lockId) {
                    return $cache->del($key);
                }
            }
        }else{
            if (!empty($this->lockId)) {
                $value = $this->redis->get($key);
                if ($value == $this->lockId) {
                    return $this->redis->del($key);
                }
            }
        }
        return false;
    }


    /**
     * 生成锁唯一ID
     * @return mixed
     */
    public function generateUniqueLockId()
    {
        $uniqid = md5(uniqid(microtime(true),true));
        return $uniqid;
    }

    public function getLock($key){
        $value = $this->redis->get($this->lockKey);
        $value = json_decode($value,true);
        $time = time();
        $timeout = intval($value['timeout']);
        $runTimeout = intval($value['runTimeout']);
        $lockListFlip = array_flip($this->lockList);
        if (empty($value) || !isset($lockListFlip[$value['rKey']])){
            return false;
        }
        if ($key != $value['rKey']){
            return false;
        }
        if ($time > $timeout && $runTimeout==0){
            return false;
        }
        if ($time > $timeout && $time > $runTimeout){
            return false;
        }
        if (isset($value['runResult']) && $value['runResult'] == 1){
            return false;
        }
        if ($key == $value['rKey'] && $runTimeout==0){
            $value['runTimeout'] = $time + $this->lockRunTimeout;
            $lockInfo = json_encode($value);
            $this->redis->set($this->lockKey,$lockInfo,$this->lockExpire);
            $this->saveLog("run ".json_encode($value));
            return true;
        }
        return false;
    }

    public function lockResult($key){
        $value = $this->redis->get($this->lockKey);
        $value = json_decode($value,true);
        $lockListFlip = array_flip($this->lockList);
        if (empty($value) || !isset($lockListFlip[$value['rKey']])){
            return false;
        }
        $runTimeout = intval($value['runTimeout']);
        if ($key == $value['rKey'] && $runTimeout>0){
            $value['runResult'] = 1;
            $lockInfo = json_encode($value);
            $this->redis->set($this->lockKey,$lockInfo,$this->lockExpire);
            $this->saveLog("runResult ".json_encode($value));
            return true;
        }
        return false;
    }

    /**
     * 设置只允许一个独立脚本访问,避免并发
     * @return bool
     */
    public function setNextLock(){
        if (empty($this->lockList)){
            return false;
        }
        $value = $this->redis->get($this->lockKey);
        $value = json_decode($value,true);
        //为空或者超时,设置下一个
        $time = time();
        $timeout = intval($value['timeout']);
        $runTimeout = intval($value['runTimeout']);
        $lockListFlip = array_flip($this->lockList);
        if (empty($value) || !isset($lockListFlip[$value['rKey']])){
            $this->saveLog("初始化");
            $this->nextLock($this->lockList[0]);
            return true;
        }
        if ($time > $timeout && $runTimeout==0){
            $this->saveLog("取锁超时".json_encode($value));
            //推送异常通知到飞书
            RiskService::notice("取锁超时".json_encode($value));
            $this->nextLock($value['rKey']);
            return true;
        }
        if ($time > $timeout && $time > $runTimeout){
            $this->saveLog("运行超时".json_encode($value));
            //推送异常通知到飞书
            RiskService::notice("运行超时".json_encode($value));
            $this->nextLock($value['rKey']);
            return true;
        }
        if (isset($value['runResult']) && $value['runResult'] == 1){
            $this->saveLog("运行完成".json_encode($value));
            $this->nextLock($value['rKey']);
            return true;
        }
        return false;
    }

    private function nextLock($key){
        $lockListFlip = array_flip($this->lockList);
        if (isset($lockListFlip[$key])){
            $nextNum = intval($lockListFlip[$key]) + 1;
            if (isset($this->lockList[$nextNum])){
                $nextKey = $this->lockList[$nextNum];
            }else{
                $nextKey = $this->lockList[0];
            }
            $nextInfo = json_encode(
                array(
                    'rKey'=>$nextKey,
                    'timeout'=>time() + $this->lockTimeout,
                    'runTimeout'=>0,//执行时间
                    'runResult'=>0,//执行结果,1已完成
                )
            );
            $this->redis->set($this->lockKey,$nextInfo,$this->lockExpire);
            $this->saveLog("setNextLock {$nextInfo}");
        }
    }

    private function saveLog($data){
        return true;
        if (!empty($data)){
            $file = APP_ROOT . '/application/data/logs/';
            if (!is_dir($file)) {
                @mkdir($file);
            }
            $fileName = date('Ym').".log";
            $str = date('Y-m-d H:i:s')."\t";
            $str .= is_array($data) ? json_encode($data) : $data;
            file_put_contents($file . $fileName, $str.PHP_EOL, FILE_APPEND);
        }
    }
}